

package tomkitty;

import java.util.*;
import java.io.*;
import java.text.*;

public class TkStaticHandler extends Turdlet implements StaticHandler {

  protected static Hashtable<String,String> cache = 
    new Hashtable<String,String>();

  private String reqFile, docRoot;
  
  //8kb buffer size - used when files are longer than this
  private final int buff_size = 8192;

  protected class MyFile extends File {
    private int cmpProperty = 1;
    private int rev = 1;

    public void setCompareProperty(int p) {cmpProperty = p;}

    public void setReverse() {rev = -1;}

    public MyFile(String f) {
      super(f);
    }

    public int compareTo(File f) {
      if (cmpProperty == 1) {
        String me = this.getName();
        String him = f.getName();
        return rev * me.compareTo(him);
      } else if (cmpProperty == 2) {
        Long me = new Long( (this.isDirectory() ? 0 : this.length()) );
        Long him = new Long( (f.isDirectory() ? 0 : f.length()) );
        return rev * me.compareTo(him);
      } else {
        Long me = new Long(this.lastModified());
        Long him = new Long(f.lastModified());
        return rev * me.compareTo(him);
      }
    }
  }

  protected String formatFileSize(long size) {
    String[] suf = {"", "K", "M", "G", "T"};
    String ret = "0";
    NumberFormat nf = NumberFormat.getInstance();
    nf.setMaximumFractionDigits(1);

    for (int i = suf.length - 1; i >= 0; i--) {
      double d = size / Math.pow(1024, i);
      if (d >= 1) {
        ret = nf.format(d) + suf[i];
        break;
      }
    }
    
    return ret;
  }
    
  protected String getContentType(File file) throws Exception {

    String filePath;

    filePath = file.getCanonicalPath();

    String mime = cache.get(filePath);

    if (mime == null) {
      mime = "text/plain";

      String argv[] = {"file", "-bi", filePath};
      Process proc = 
        Runtime.getRuntime().exec(argv);
      BufferedReader br = 
        new BufferedReader(new InputStreamReader(proc.getInputStream()));
      mime = br.readLine();
      cache.put(filePath, mime);
      br.close();
    }
    
    // this is because FF at least wants to download all kinds of text. dumb.
    if ( (mime.indexOf("/") == -1) || 
         (mime.length() > 6 && mime.substring(0, 6).equals("text/x")) ) {
      mime = "text/plain";
    }

    return mime;
  }

  public void doit() {
    reqFile = request.getUri().getPath();

    formatFileSize(0);
    // get the requested languages from client
    String langField = request.getField("Accept-Language");
    String[] langs = langField.split(",");

    // disregard quality thingers in language field for now (FIXME)
    for (int i = 0; i < langs.length; i++) {
      langs[i] = (langs[i].equals("") ? "" : ".") + langs[i].split(";")[0];
    }

    // segments[1] is expected to be the app name, so we remove that part
    String[] segments = reqFile.split("/", 3);
    if (segments.length > 2) {
      reqFile = "/" + segments[2];
    } else {
      reqFile = "/";
    }

    // document root
    docRoot = context.getConfig().get(CONF_DOCROOT);

    // default file (i.e. index.html or something)
    String dflFile = context.getConfig().get(CONF_DEFAULT_FILE);

    if (dflFile == null) {
      dflFile = "";
    }

    try {

      // check for the actual file, or otherwise one of the default files
      File file = null;

      // check for requested and default file (with language suffix and 
      // without)
      for (int i = 0; i < langs.length; i++) {
        file = new File(docRoot + "/" + reqFile + langs[i]);
        //Debug.d("trying '"+file.getName()+"'");
        if (file.exists()) {
          break;
        }

        file = new File(docRoot + "/" + reqFile + "/" + dflFile + langs[i]);
        //Debug.d("trying '"+file.getName()+"'");
        if (file.exists()) {
          dflFile = file.getName();
          break;
        }
      }

      // check for requested file
      if (file == null || ( !file.exists() && 
            !(file = 
              new File(docRoot + "/" + reqFile + "/" + dflFile)).exists() &&
            !(file = new File(docRoot + "/" + reqFile)).exists()) ) {
        throw new TkResponseException(HTTP_NOT_FOUND);
      }
    
      String query = request.getOrigUri().getQuery();
      String fragment = request.getOrigUri().getFragment();
      String newpath;

      if (file.isDirectory()) {
        String uripath = request.getUri().getPath();
        if (uripath.length() < 1 || uripath.charAt(uripath.length()-1) != '/') {
          newpath =   request.getOrigUri().getScheme() + "://" + 
                      request.getOrigUri().getAuthority() + 
                      request.getOrigUri().getPath() + "/" +
                      (query != null ? "?" + query : "") +
                      (fragment != null ? "#" + fragment : "");
          response.setField("Location", newpath);
          throw new TkResponseException(HTTP_MOVED_PERM);
        }
        dirList(file);
      } else if (file.canRead()) {

        // if is an index file, and path doesn't end in /, rewrite
        if(file.getName().equals(dflFile)) {
          String uripath = request.getUri().getPath();
          if (uripath.length() < 1 || 
              (! uripath.endsWith(dflFile) && 
               uripath.charAt(uripath.length() - 1) != '/')) {
            newpath =   request.getOrigUri().getScheme() + "://" + 
                        request.getOrigUri().getAuthority() + 
                        request.getOrigUri().getPath() + "/" +
                        (query != null ? "?" + query : "") +
                        (fragment != null ? "#" + fragment : "");
            response.setField("Location", newpath);
            throw new TkResponseException(HTTP_MOVED_PERM);
          }
        }
        
        int len = (int) file.length();
        
        response.setField("Content-Type", getContentType(file));
        response.setField("Content-Length", new Integer(len).toString());

        BufferedInputStream br = new BufferedInputStream(new FileInputStream (file));
        OutputStream os = response.getSocket().getOutputStream();
        
        byte[] buffer;
        //file size is less than buffer, not buffering
        if(len < buff_size) {
          buffer = new byte[len];
          br.read(buffer, 0, len);
          os.write(buffer, 0, len);
        } else {
          //dealing with a file bigger than buffer size, buffering
          buffer = new byte[buff_size];
          while(br.read(buffer, 0, buff_size) != -1) {
            os.write(buffer, 0, buff_size);
          }
        }
        
        br.close ();
      } else {
        throw new TkResponseException(HTTP_FORBIDDEN);
      }

    } catch (TkException tk_e) {

      response.setStatus(tk_e.getMessage());

    } catch (Exception e) {

      e.printStackTrace();
      response.setStatus(HTTP_SERVER_ERROR);

    }

  }

  protected void dirList(File dir) throws Exception {

    String dirPath = dir.getCanonicalPath();
    String canonDocRoot = (new File(docRoot)).getCanonicalPath();
    
    File[] flist = dir.listFiles();
    MyFile[] list = new MyFile[flist.length];

    String query;
    int orderby = 1;
    boolean reverse = false;
    if ((query = request.getOrigUri().getQuery()) != null) {
      String[] queries = query.split("&");
      if (queries.length > 1) {
        if (queries[1].equals("descending")) {
          reverse = true;
        }
      }
      if (queries[0].equals("orderby=size")) {
        orderby = 2;
      } else if (queries[0].equals("orderby=date")) {
        orderby = 3;
      }
    }

    for (int z = 0; z < list.length; z++) {
      list[z] = new MyFile(flist[z].getPath());
      list[z].setCompareProperty(orderby);
      if (reverse) {
        list[z].setReverse();
      }
    }

    Arrays.sort(list);

    String path = request.getUri().getPath();
    String origpath = request.getOrigUri().getPath();
    String host = request.getOrigUri().getAuthority();
    SimpleDateFormat df = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss z");

    Tag html = new Tag("html");
    Tag head = html.newChild("head");
    Tag body = html.newChild("body");

    Tag title = head.newChild("title");
    title.setContent("Index of //" + host + origpath);

    Tag style = head.newChild("style");
    style.setAttribute("type", "text/css");
    style.setContent("a {text-decoration: none; font-family: monospace;}\n"
        + ".main_table {padding-left: 20px; padding-right: 20px; "
        + " padding-bottom: 20px; font-family: monospace;}\n"
        + "h1 {padding-left: 20px; padding-right: 20px; padding-top: 20px; "
        + "font-family: monospace;}");

    Tag h1 = body.newChild("h1");
    h1.setContent(origpath);

    Tag tr, td, td1, td2, td3, th, hr, a;

    Tag table = body.newChild("table");
    table.setAttribute("class", "main_table");
    table.setAttribute("width", "100%");

    tr = table.newChild("tr");
    
    th = tr.newChild("th");
    th.setAttribute("width", "40%");

    a = th.newChild("a");
    a.setAttribute("href", 
        request.getOrigUri().getPath() + "?orderby=name" +
        (!reverse && orderby == 1 ? "&descending" : ""));
    a.setContent("Name");

    th = tr.newChild("th");
    th.setAttribute("width", "20%");
    th.setAttribute("align", "right");

    a = th.newChild("a");
    a.setAttribute("href", request.getOrigUri().getPath() + "?orderby=size" +
        (!reverse && orderby == 2 ? "&descending" : ""));
    a.setContent("Size");

    th = tr.newChild("th");
    th.setAttribute("width", "40%");

    a = th.newChild("a");
    a.setAttribute("href", request.getOrigUri().getPath() + "?orderby=date" +
        (!reverse && orderby == 3 ? "&descending" : ""));
    a.setContent("Last Modified");

    tr = table.newChild("tr");
    td = tr.newChild("td");
    td.setAttribute("colspan", "3");

    hr = td.newChild("hr");
    hr.setAttribute("noshade", "true");
    hr.setAttribute("size", "1px");

    if (! dirPath.equals(canonDocRoot)) {
      tr = table.newChild("tr");

      td = tr.newChild("td");
      td.setAttribute("width", "40%");

      a = td.newChild("a");
      a.setAttribute("href", "..");
      a.setContent("../");
    }

    for(int i = 0; i < list.length; i++) {
      MyFile f = list[i];
      String fname = f.getName();
      long flen = f.length();
      long flmod = f.lastModified();
      String nohide = context.getConfig().get(CONF_LIST_HIDDEN);

      if (f.isHidden() && nohide.equals("false")) {
        continue;
      }

      if (nohide == null) {
        nohide = "false";
      }

      tr = table.newChild("tr");

      td1 = tr.newChild("td");
      td1.setAttribute("width", "40%");

      a = td1.newChild("a");

      td2 = tr.newChild("td");
      td2.setAttribute("width", "20%");
      td2.setAttribute("align", "right");

      td3 = tr.newChild("td");
      td3.setAttribute("width", "40%");
      td3.setAttribute("align", "right");
      td3.setContent(df.format(new Date(flmod)));

      if(f.isDirectory()) {
        a.setAttribute("href", fname + "/");
        a.setContent(fname + "/");
        td2.setContent("-");
      } else {
        a.setAttribute("href", fname);
        a.setContent(fname);
        td2.setContent(formatFileSize(f.length()));
      }
    } 

    tr = table.newChild("tr");
    td = tr.newChild("td");
    td.setAttribute("colspan", "3");

    hr = td.newChild("hr");
    hr.setAttribute("noshade", "true");
    hr.setAttribute("size", "1px");

    tr = table.newChild("tr");
    td = tr.newChild("td");
    td.setAttribute("colspan", "3");
    td.setAttribute("align", "left");
    td.setContent(Main.name + "/" + Main.version + " port " + context.getConfig().get(CONF_PORT));

    response.setStatus(HTTP_OK);
    println(html.render());
  }
}

